package com.zwl.swagger;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.zwl.util.ReflectUtil;
import com.zwl.util.SwaggerSpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.plugin.core.Plugin;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhangweilin
 * @description: zwlx
 * @date 2018/10/31
 */
@Slf4j
public abstract class BaseSwagger2Config {

    @Autowired
	SwaggerProperties swaggerProperties;

    private AntPathMatcher springAntMatcher = new AntPathMatcher();

    @Autowired
    Environment environment;

    @Bean
    public Docket userApi() {
        SwaggerProperties.Swagger2Properties swagger = swaggerProperties.getCore();
        return getDocket(environment.getProperty("spring.application.name"), swagger.getBasePackages());
    }

    protected List<ApiKey> securitySchemes() {
        SwaggerProperties.Swagger2Properties swagger = swaggerProperties.getCore();
		List<String> globalHeaderKeyList = swagger.getGlobalHeaderKey();
		List<ApiKey> apiKeyList = new ArrayList<>(globalHeaderKeyList.size());
		for (String globalHeaderKey : globalHeaderKeyList) {
			ApiKey apiKey = new ApiKey(globalHeaderKey, globalHeaderKey, "header");
			apiKeyList.add(apiKey);
		}
        return apiKeyList;
    }

    protected List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.any()).build());
        return securityContexts;
    }

    private List<SecurityReference> defaultAuth() {
        SwaggerProperties.Swagger2Properties swagger = swaggerProperties.getCore();
        String tokenKey = swagger.getTokenKey();

        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        //此处tokenKey要和ApiKey中一致，否则不会做为header传到后台
        securityReferences.add(new SecurityReference(tokenKey, authorizationScopes));
        return securityReferences;
    }


    protected Docket getDocket(final String groupName, final List<String> basePackageList) {
        Assert.notEmpty(basePackageList, "请先配置swagger文档basePackage,支持ant风格路径，如sys.swagger.base-packages=com.zwl.**.controller,com.zwl.xxx.controller,com.zwl.common.controller");
        Docket docket = new Docket(DocumentationType.SWAGGER_2);
        docket.apiInfo(apiInfo(groupName)).forCodeGeneration(true).select()
                .apis(basePackage(basePackageList.toArray(new String[]{}))).paths(PathSelectors.any())
                .build().groupName(groupName).securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
        //要加上.globalOperationParameters(pars);才会在全局显示
		SwaggerProperties.Swagger2Properties swagger = swaggerProperties.getCore();
		boolean docSwitch = swagger.isDocSwitch();
		log.info("swagger = " + swagger+" , docSwitch: "+docSwitch);
        try {
			if (docSwitch) {
				resetDefaultPlugin();
			}
		} catch (Exception e) {
            e.printStackTrace();
            System.err.println("zwl-swagger出了点小问题"+e.getMessage());
        }
        return docket;
    }

    private void resetDefaultPlugin() {
        //此影响了类上的注释
		log.info("初始化 operationBuilderPluginRegistry");
        PluginRegistry<Plugin<DocumentationType>, DocumentationType> operationBuilderPluginRegistry =
                SwaggerSpringUtil.getBean("operationBuilderPluginRegistry", PluginRegistry.class);
        autoReset(operationBuilderPluginRegistry);

		log.info("初始化 modelPropertyBuilderPluginRegistry");
        PluginRegistry<Plugin<DocumentationType>, DocumentationType> modelPropertyBuilderPluginRegistry =
                SwaggerSpringUtil.getBean("modelPropertyBuilderPluginRegistry", PluginRegistry.class);
        autoReset(modelPropertyBuilderPluginRegistry);

		log.info("初始化 apiListingBuilderPluginRegistry");
        PluginRegistry<Plugin<DocumentationType>, DocumentationType> apiListingBuilderRegistry =
                SwaggerSpringUtil.getBean("apiListingBuilderPluginRegistry", PluginRegistry.class);
        autoReset(apiListingBuilderRegistry);

		log.info("初始化 parameterBuilderPluginRegistry");
        PluginRegistry<Plugin<DocumentationType>, DocumentationType> parameterBuilderPluginRegistry =
                SwaggerSpringUtil.getBean("parameterBuilderPluginRegistry", PluginRegistry.class);
        autoReset(parameterBuilderPluginRegistry);

		log.info("初始化 expandedParameterBuilderPluginRegistry");
        PluginRegistry<Plugin<DocumentationType>, DocumentationType> expandedParameterBuilderRegistry =
                SwaggerSpringUtil.getBean("expandedParameterBuilderPluginRegistry", PluginRegistry.class);
        autoReset(expandedParameterBuilderRegistry);
		CommentReader.readDoc();
	}

    private void autoReset(PluginRegistry<Plugin<DocumentationType>, DocumentationType> operationBuilderPluginRegistry) {
        Map<String, Plugin<DocumentationType>> builderPluginMap = new HashMap<>();
        // 插件集合
        List<String> toReplaceNameList = new ArrayList<>();
        List<Plugin<DocumentationType>> plugins = operationBuilderPluginRegistry.getPlugins();
        for (Plugin<DocumentationType> plugin : plugins) {
            String name = plugin.getClass().getSimpleName();
            builderPluginMap.put(name, plugin);
            if (name.endsWith("Plus")) {
                String toReplaceName = name.replace("Plus", "");
                toReplaceNameList.add(toReplaceName);
            }
        }
        for (String toReplaceName : toReplaceNameList) {
            builderPluginMap.remove(toReplaceName);
        }
        if (!CollectionUtils.isEmpty(toReplaceNameList)) {
            List<Plugin<DocumentationType>> newPluginsList1 = new ArrayList<>(builderPluginMap.values());
            ReflectUtil.setFieldValue(operationBuilderPluginRegistry,"plugins",newPluginsList1);
        }
    }

    /**
     * @param groupName
     * @param basePackage 支持配置多个包，每个包支持ant风格表达式
     * @return
     */
    protected Docket getDocket(final String groupName, final String... basePackage) {
        Docket docket = new Docket(DocumentationType.SWAGGER_2);
        docket.apiInfo(apiInfo(groupName)).forCodeGeneration(true).select()
                .apis(basePackage(basePackage)).paths(PathSelectors.any())
                .build().groupName(groupName).securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
        //要加上.globalOperationParameters(pars);才会在全局显示
        return docket;
    }

    protected abstract ApiInfo apiInfo(String groupName);


    /**
     * 重写basePackage方法，使能够实现多包访问，
     *
     * @param [basePackage]
     * @return com.google.common.base.Predicate<springfox.documentation.RequestHandler>
     * @author teavamc
     * @date 2019/1/26
     */
    protected Predicate<RequestHandler> basePackage(final String... basePackage) {
        return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
    }

    protected Function<Class<?>, Boolean> handlerPackage(final String... basePackage) {
        return input -> {
            for (String strPackage : basePackage) {
                String basePackageName = input.getPackage().getName();
                boolean isMatch = springAntMatcher.match(strPackage, basePackageName);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    protected Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }
}

